﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Vector3 = DDD.Vector3<float>;

namespace DDD {
    /// <summary>
    ///  4要素のベクトル構造体
    /// </summary>
    public struct Vector4<T> : IExportable<T>, IAnimatable<T>, IEquatable<Vector4<T>> where T : struct {

        #region Constructor
        /// <summary>
        /// 要素を個別に指定するコンストラクター
        /// </summary>
        /// <param name="x">X要素</param>
        /// <param name="y">Y要素</param>
        /// <param name="z">Z要素</param>
        /// <param name="w">W要素</param>
        public Vector4 (T x, T y, T z, T w)
            : this () {
            this.X = x;
            this.Y = y;
            this.Z = z;
            this.W = w;
        }

        /// <summary>
        /// 要素を配列で指定するコンストラクター
        /// </summary>
        /// <param name="xyzw">XYZW要素</param>
        public Vector4 (T[] xyzw)
            : this () {
            this.X = xyzw[0];
            this.Y = xyzw[1];
            this.Z = xyzw[2];
            this.W = xyzw[3];
        }

        /// <summary>
        /// 要素を<see cref="Vector3"/>とfloatで指定するコンストラクター
        /// </summary>
        /// <param name="v">3要素のベクトル</param>
        /// <param name="w">w要素</param>
        public Vector4 (Vector3<T> v, T w)
            : this () {
            this.X = v.X;
            this.Y = v.Y;
            this.Z = v.Z;
            this.W = w;
        }
        #endregion

        #region Property
        /// <summary>
        /// X要素
        /// </summary>
        public T X {
            get;
            private set;
        }

        /// <summary>
        /// Y要素
        /// </summary>
        public T Y {
            get;
            private set;
        }

        /// <summary>
        /// Z要素
        /// </summary>
        public T Z {
            get;
            private set;
        }

        /// <summary>
        /// W要素
        /// </summary>
        public T W {
            get;
            private set;
        }


        /// <summary>
        /// コンポーネント数
        /// </summary>
        public int ComponentCount {
            get { return 4; }
        }

        /// <summary>
        /// コンポーネントにアクセスするインデクサー
        /// </summary>
        /// <param name="index">インデックス</param>
        /// <returns></returns>
        public T this[int index] {
            get {
                if (index < 0 || index >= ComponentCount) {
                    throw new IndexOutOfRangeException ("Index is out of range");
                }
                switch (index) {
                    case 0: return X;
                    case 1: return Y;
                    case 2: return Z;
                    case 3: return W;
                    default: return default(T);
                }
            }
            set {
                if (index < 0 || index >= ComponentCount) {
                    throw new IndexOutOfRangeException ("Index is out of range");
                }
                switch (index) {
                    case 0: X = value; break;
                    case 1: Y = value; break;
                    case 2: Z = value; break;
                    case 3: W = value; break;
                    default: break;
                }
            }
        }
        #endregion

        #region Method

        /// <summary>
        /// W除算を行い等価な<see cref="Vector3"/>型のベクトルを新たに作成します
        /// </summary>
        /// <returns><see cref="Vector3"/>型のベクトル</returns>
        public Vector3<T> DividedByW () {
            if ((dynamic)W == 0) {
                throw new InvalidOperationException ("Divided by W==0");
            }
            return new Vector3<T> ((dynamic)X / W, (dynamic)Y / W, (dynamic)Z / W);
        }


        /// <summary>
        /// 全要素をTの配列に変換します
        /// </summary>
        /// <param name="v">変換元のベクトル</param>
        /// <returns>変換後のfloatの配列</returns>
        public static explicit operator T[] (Vector4<T> v) {
            return new T[] { v[0], v[1], v[2], v[3] };
        }

        /// <summary>
        /// 上方向の単位ベクトル
        /// </summary>
        /// <returns></returns>
        public static Vector4<T> Up {
            get { return new Vector4<T> ((dynamic)0, (dynamic)1, (dynamic)0, (dynamic)1); }
        }

        /// <summary>
        /// 下方向の単位ベクトル
        /// </summary>
        /// <returns></returns>  
        public static Vector4<T> Down {
            get { return new Vector4<T> ((dynamic)0, (dynamic)(-1), (dynamic)0, (dynamic)1); }
        }

        /// <summary>
        /// 右方向の単位ベクトル
        /// </summary>
        /// <returns></returns>
        public static Vector4<T> Right {
            get { return new Vector4<T> ((dynamic)1, (dynamic)0, (dynamic)0, (dynamic)1); }
        }

        /// <summary>
        /// 左方向の単位ベクトル
        /// </summary>
        /// <returns></returns>
        public static Vector4<T> Left {
            get { return new Vector4<T> ((dynamic)(-1), (dynamic)0, (dynamic)0, (dynamic)1); }
        }

        /// <summary>
        /// 前方向の単位ベクトル
        /// </summary>
        /// <returns></returns>
        public static Vector4<T> Forward {
            get { return new Vector4<T> ((dynamic)0, (dynamic)0, (dynamic)(-1), (dynamic)1); }
        }

        /// <summary>
        /// 後ろ方向の単位ベクトル
        /// </summary>
        /// <returns></returns>
        public static Vector4<T> Back {
            get { return new Vector4<T> ((dynamic)0, (dynamic)0, (dynamic)1, (dynamic)1); }
        }

        /// <summary>
        /// 原点(0,0,0,1)を返します
        /// </summary>
        /// <returns></returns>
        public static Vector4<T> Zero {
            get { return new Vector4<T> ((dynamic)0, (dynamic)0, (dynamic)0, (dynamic)1); }
        }

        /// <summary>
        /// 同値性を比較します
        /// </summary>
        /// <remarks>
        /// 同値性を比較する汎用の比較関数です。
        /// <see cref="Vector4{T}"/>オブジェクトはコンポーネント要素が厳密に一致しなくても、
        /// 全てのコンポーネント要素で差が<see cref="GlobalSettings.Tolerance"/>以下であれば等しいと見なされます。
        /// </remarks>
        /// <param name="obj">右辺のオブジェクト</param>
        /// <returns>等しいときtrue, そうでないときfalse</returns>
        public override bool Equals (object obj) {
            if (object.ReferenceEquals (obj, null)) {
                return false;
            }
            if (object.ReferenceEquals (obj, this)) {
                return true;
            }
            var v = obj as Vector4<T>?;
            if (v == null) {
                return false;
            }

            return this.Equals (v.Value);
        }

        /// <summary>
        /// タイプセーフな同値性を比較します
        /// </summary>
        /// <remarks>
        /// 
        /// </remarks>
        /// <param name="v">右辺の<see cref="Vector4{T}"/>オブジェクト</param>
        /// <returns>等しいときtrue, そうでないときfalse</returns>
        public bool Equals (Vector4<T> v) {
            return (Math.Abs ((dynamic)X - v.X) < GlobalSettings.Tolerance) &&
                    (Math.Abs ((dynamic)Y - v.Y) < GlobalSettings.Tolerance) &&
                     (Math.Abs ((dynamic)Z - v.Z) < GlobalSettings.Tolerance) &&
                     (Math.Abs ((dynamic)W - v.W) < GlobalSettings.Tolerance);
        }

        /// <summary>
        /// 等号による同値性を比較します
        /// </summary>
        /// <param name="left">左項</param>
        /// <param name="right">右項</param>
        /// <returns>等しいときtrue, そうでないときfalse</returns>
        public static bool operator == (Vector4<T> left, Vector4<T> right) {
            return left.Equals (right);
        }

        /// <summary>
        /// 等号による同値性を比較します
        /// </summary>
        /// <param name="left">左項</param>
        /// <param name="right">右項</param>
        /// <returns>等しくないときtrue, そうでないときfalse</returns>
        public static bool operator != (Vector4<T> left, Vector4<T> right) {
            return !(left == right);
        }

        /// <summary>
        /// 一意のハッシュ値を取得します
        /// </summary>
        /// <remarks>
        /// このメソッドは同値性の比較メソッドと異なり
        /// <see cref="GlobalSettings.Tolerance"/>の影響を受けません。
        /// </remarks>
        /// <returns></returns>
        public override int GetHashCode () {
            return base.GetHashCode ();
        }


        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary>
        /// <returns>現在のオブジェクトを説明する文字列</returns>
        public override string ToString () {
            return string.Format ("Vector4: X={0}, Y={1}, Z={2}, W={3}", X, Y, Z, W);
        }
        #endregion

    }
}
